Zum Hauptinhalt springen

Einheit 3 — Der Router: Regeln zuerst, KI als Rückfall

Was du nach dieser Einheit weißt: Du kannst einen Router bauen, der eingehende Nachrichten in fester Reihenfolge prüft, frei formulierte Anfragen per KI einem Skill zuordnet, bei Unsicherheit nachfragt und riskante Aktionen erst nach Bestätigung ausführt.

Die Aufgabe des Routers​

Der Router beantwortet für jede Nachricht genau eine Frage: Was passiert als Nächstes? Mögliche Ergebnisse sind:

ErgebnisBeispiel
Direkte Antwort„Hilfe“ → Liste der Fähigkeiten
Weitergabe an einen Skill„Wie hoch ist der Umsatz im Oktober?“ → Skill Kennzahlen
Rückfrage„Mach das mal fertig“ → „Meinst du das Angebot für Firma X?“
Bestätigung einholen„Schick die Mahnung raus“ → „Soll ich die Mahnung an … senden? ja/nein“
Ablehnungunbekannter Absender, Assistent pausiert

Der Router führt selbst nichts aus. Er entscheidet und verteilt.

Der Aufbau im Workflow​

[ Eingang ] → [ Kontext laden ] → [ Entscheiden ] → [ Verteilen ] ─→ Skill Wissen
Internal Storage JavaScript Switch Agent ─→ Skill Erinnerung
▲ ─→ Rückkanal (direkte Antwort)
│ ─→ KI-Erkennung ─┐
└─────────── Ergebnis der KI ───────────────┘

Auffällig ist die Schleife: Wenn keine feste Regel greift, geht die Nachricht zur KI-Erkennung und kommt mit deren Ergebnis ein zweites Mal in denselben Entscheidungs-Agent zurück. So steht die gesamte Entscheidungslogik an einer Stelle.

Schritt 1 — Kontext in einem Zug laden​

Ein einziger Internal Storage Agent holt alles, was der Router braucht. Jede Unterabfrage liefert eine Spalte:

SELECT
(SELECT COUNT(*) FROM chat_nachricht WHERE nachricht_id = '{{ nachricht_id }}') AS schon,
(SELECT wert FROM assistent_einstellung WHERE schluessel = 'aktiv') AS aktiv,
(SELECT rolle FROM assistent_nutzer
WHERE kanal = '{{ kanal }}' AND absender_id = '{{ absender_id }}' AND aktiv = 1) AS rolle,
(SELECT json_group_array(json_object('s', schluessel, 'n', name, 'b', beschreibung,
'x', beispiele, 'r', risiko, 'z', ziel))
FROM (SELECT * FROM assistent_faehigkeit WHERE aktiv = 1 ORDER BY sortierung)) AS faehigkeiten,
(SELECT json_group_array(json_object('r', rolle, 't', text))
FROM (SELECT rolle, text FROM assistent_verlauf
WHERE unterhaltung_id = '{{ unterhaltung_id }}' ORDER BY id DESC LIMIT 8)) AS verlauf,
(SELECT json_object('id', id, 'f', faehigkeit, 't', text, 's', status)
FROM assistent_vorgang
WHERE unterhaltung_id = '{{ unterhaltung_id }}'
AND status IN ('bestaetigung', 'rueckfrage')
AND erstellt > datetime('now', '-30 minutes')
ORDER BY erstellt DESC LIMIT 1) AS offen

Setze am Internal Storage Agent keep_message: true. Dann enthält die ausgehende Nachricht sowohl die ursprünglichen Felder (text, absender_id …) als auch das Abfrageergebnis unter rows.

Die Tabellen assistent_faehigkeit, assistent_verlauf und assistent_vorgang lernst du in den Einheiten 4 und 5 kennen.

Schritt 2 — Entscheiden in fester Reihenfolge​

Der Entscheidungs-Agent ist ein JavaScript Agent. Er prüft die Nachricht von oben nach unten — die erste passende Regel gewinnt:

#PrüfungErgebnis
1Nachricht schon verarbeitet?still beenden
2Absender nicht berechtigt?Ablehnung
3Steuerbefehl („pause“, „weiter“)?Assistent an- oder abschalten
4Assistent pausiert?Hinweis, nichts ausführen
5Offene Bestätigung und Antwort „ja“/„nein“?gemerkten Auftrag ausführen oder verwerfen
6Feste Befehle („hilfe“, „was läuft?“)direkte Antwort
7Eindeutige Muster („erinnere mich …“, „neues Ticket …“)Skill direkt
8Gelernte Formulierung (Einheit 6)Skill direkt
9Nichts davonKI-Erkennung

Diese Reihenfolge ist kein Zufall:

  • Sicherheit vor allem anderen. Doppelte Nachrichten, fremde Absender und der Not-Aus werden geprüft, bevor irgendetwas anderes passiert.
  • Offene Gespräche vor neuen. Wer gerade gefragt wurde „Soll ich das ausführen?“ und mit „ja“ antwortet, meint damit diese Frage — und keinen neuen Auftrag.
  • Billig vor teuer. Regeln kosten nichts und sind sofort da. Die KI kommt nur zum Zug, wenn keine Regel passt.

Ein gekürztes Beispiel:

Agent.receive = function() {
var p = this.incomingMessage().payload || {};
var r0 = (p.rows && p.rows[0]) || {};
var ki = p.ki || null; // gesetzt im zweiten Durchlauf
var text = String(p.text || '').trim();
var o = kopie(p); // Felder für die nächsten Schritte

function json(v, d) { try { return typeof v === 'string' ? JSON.parse(v) : (v || d); } catch (e) { return d; } }
function kopie(x) { var c = {}; for (var k in x) { if (k !== 'rows') { c[k] = x[k]; } } return c; }

var katalog = ki ? p.katalog : json(r0.faehigkeiten, []);
var offen = ki ? null : json(r0.offen, null);
o.katalog = katalog;

if (ki) { return zweiterDurchlauf(this); }

if (parseInt(r0.schon, 10) > 0) { return; } // 1
if (!r0.rolle) { return antworte(this, 'Deine Nachricht kann ich nicht verarbeiten.'); } // 2
if (/^(pause|stopp)$/i.test(text)) { o.sql_zusatz = setzeAktiv(0); return antworte(this, '⏸ Pausiert.'); } // 3
if (/^(weiter|start)$/i.test(text)) { o.sql_zusatz = setzeAktiv(1); return antworte(this, '▶ Wieder aktiv.'); }
if (r0.aktiv === '0') { return antworte(this, '⏸ Ich bin pausiert. Schreib „weiter“.'); } // 4
if (offen && offen.s === 'bestaetigung' && /^(ja|ok|mach)/i.test(text)) { return fuehreAus(this, offen.f, offen.t); } // 5
if (/^(hilfe|\?|was kannst du)/i.test(text)) { return antworte(this, hilfeText(katalog)); } // 6
if (/^erinnere? mich\b/i.test(text)) { return weiter(this, 'erinnerung'); } // 7

// 9: keine Regel — an die KI-Erkennung
o.route = 'ki';
o.ki_katalog = katalog.map(function(k) { return '- ' + k.s + ': ' + k.b + ' | ' + (k.x || ''); }).join('\n');
this.createMessage(o);

// … Hilfsfunktionen antworte(), weiter(), fuehreAus(), zweiterDurchlauf() siehe Übung 1
};

Die vollständige, lauffähige Fassung baust du in Übung 1.

Schritt 3 — Die KI-Erkennung​

Greift keine Regel, ordnet ein Generative AI Agent die Nachricht einem Skill zu. Der Prompt bekommt drei Dinge: den Katalog der Fähigkeiten, den bisherigen Verlauf und die neue Nachricht.

Du ordnest Nachrichten an einen digitalen Assistenten genau einer Fähigkeit zu.

FÄHIGKEITEN (schluessel: Beschreibung | Beispiele)
{{ ki_katalog }}

BISHERIGER VERLAUF (älteste zuerst)
{{ ki_verlauf }}

NEUE NACHRICHT
<<<
{{ text }}
>>>

Regeln:
- Passt keine Fähigkeit, antworte mit "unbekannt".
- Wenn du raten müsstest, setze "sicherheit" unter 0.6.
- Nenne in "alternativen" bis zu zwei weitere Fähigkeiten, die auch passen könnten.

Antworte ausschließlich mit diesem JSON-Objekt:
{"faehigkeit": "<schluessel oder unbekannt>", "sicherheit": <0 bis 1>,
"alternativen": ["<schluessel>"], "zusammenfassung": "<ein Satz, was gewünscht ist>"}

Stelle den Agent auf output_format: json und aktiviere emit_on_error. Dann kommt auch bei einem Modellfehler eine Nachricht an — mit success: false — und der Router kann reagieren, statt stumm zu bleiben.

Ein kleiner JavaScript Agent nach der KI liest das Ergebnis aus, macht genau einen Wiederholversuch, wenn das Modell nicht oder unbrauchbar geantwortet hat, und schickt das Ergebnis als Feld ki zurück an den Entscheidungs-Agent.

Die Schleife absichern

Jede Schleife zwischen Agents braucht eine Abbruchbedingung. Zähle die Versuche in einem Feld (ki_versuch) und lass den Switch Agent nach dem ersten Wiederholversuch nicht mehr zurück zur KI verzweigen. Sonst kann ein dauerhaft gestörtes Modell eine Endlosschleife auslösen.

Schritt 4 — Den KI-Vorschlag bewerten​

Im zweiten Durchlauf prüft der Router den Vorschlag der KI — wieder mit festen Regeln:

SituationReaktion
Modell nicht erreichbarehrliche Fehlermeldung, Vorgang auf fehler
unbekannt„Das kann ich noch nicht.“ Wunsch wird notiert (Einheit 6)
sicherheit < 0,6Rückfrage mit Auswahl
Skill mit Risikostufe ≥ 2Bestätigung einholen
sonstan den Skill weitergeben
Die „Sicherheit“ ist eine Selbsteinschätzung

Sprachmodelle schätzen ihre eigene Sicherheit nicht zuverlässig ein. Der Wert ist ein grober Hinweis, keine Wahrscheinlichkeit. Verlass dich nicht allein darauf: Die Risikostufe des Skills entscheidet unabhängig davon, ob vor dem Ausführen gefragt wird.

Rückfragen mit Auswahl​

Eine offene Rückfrage („Was meinst du genau?“) führt oft zu einer ebenso unklaren Antwort. Besser ist eine Auswahl:

Das habe ich nicht sicher verstanden. Meinst du: 1 Kennzahl abfragen (Umsatz Oktober) 2 Firmenauskunft Antworte mit der Nummer oder schreib es genauer.

Antwortet die Person mit „1“, weiß der Router sofort Bescheid. Und — das ist der eigentliche Gewinn — er hat ein Lernbeispiel gewonnen: Diese Formulierung bedeutet „Kennzahl abfragen“. Wie er sich das merkt, zeigt Einheit 6.

Bestätigungen​

Bei riskanten Skills (Mails versenden, Daten in fremden Systemen ändern) antwortet der Router zuerst mit einer Zusammenfassung:

Ich würde Folgendes tun: Zahlungserinnerung senden – an Muster GmbH, Rechnung 2026-117. Soll ich das ausführen? Antworte mit ja oder nein.

Der Auftrag wird als Vorgang mit Status bestaetigung gespeichert. Kommt innerhalb von 30 Minuten ein „ja“, führt Regel 5 ihn aus. Kommt etwas anderes, verfällt er.

Schritt 5 — Verteilen​

Ein Switch Agent verteilt anhand des Feldes route:

EmpfängerBedingung
Rückkanal'{{ route }}' == 'antwort'
KI-Erkennung'{{ route }}' == 'ki'
Skill Wissen'{{ route }}' == 'wissen'
Skill Erinnerung'{{ route }}' == 'erinnerung'

Parallel schreiben drei Internal Storage Agents die Ergebnisse des Routers fort: Nachricht als verarbeitet markieren, Verlauf ergänzen, Vorgang anlegen oder aktualisieren. Der Entscheidungs-Agent liefert die fertigen SQL-Befehle dafür in Feldern mit (sql_merken, sql_verlauf, sql_vorgang), die Storage Agents setzen sie nur ein (sql_statement: {{ sql_vorgang }}).

Kurzer Selbst-Check​

Jemand schreibt „ja“, ohne dass vorher eine Bestätigung offen war. Was passiert?

Regel 5 greift nicht, weil kein offener Vorgang mit Status bestaetigung existiert. Die Nachricht fällt bis zur KI-Erkennung durch, die mangels Zusammenhang „unbekannt“ oder eine niedrige Sicherheit liefert — der Assistent fragt nach.

Warum ist die Bestätigungsfrist auf 30 Minuten begrenzt?

Ein „ja“ am nächsten Morgen bezieht sich wahrscheinlich auf etwas anderes. Eine abgelaufene Bestätigung ist sicherer als eine, die unerwartet noch Tage später ausgelöst wird.